package com.dhp.bkapigateway.gateway;

import cn.hutool.json.JSONUtil;
import com.dhp.bkapigateway.Utils.SignUtil;
import com.dhp.bkapigateway.common.ErrorCode;
import com.dhp.bkapigateway.constant.RedisConstant;
import com.dhp.bkapigateway.exception.BusinessException;
import com.dhp.bkapigateway.model.RedisAccess;
import com.dhp.model.entity.InterfaceInfo;
import com.dhp.model.entity.User;
import com.dhp.service.InnerInterfaceInfoService;
import com.dhp.service.InnerUserInterfaceInfoService;
import com.dhp.service.InnerUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 全局过滤器，进行鉴权以及接口调用次数更新业务
 */
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
    //private static final String FAKE_SK = "dhpbk201";

    private static final Long THREE_MINUTES = 3 * 60L;

    @DubboReference
    private InnerUserService innerUserService;

    @DubboReference
    private InnerInterfaceInfoService interfaceInfoService;

    @DubboReference
    private InnerUserInterfaceInfoService userInterfaceInfoService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        //0. 接口防抖
        preventFrequentRequest(request);

        //1. 打印请求日志
        requestLog(request);

        //2. 验证参数
        validateRequest(request);

        //3. 鉴权
        String accessKey = request.getHeaders().getFirst(HeadersConstant.ACCESS_KEY);
        String sign = request.getHeaders().getFirst(HeadersConstant.SIGN);
        String originUrl = request.getHeaders().getFirst(HeadersConstant.INVOKE_INTERFACE);
        // 获取接口信息
        InterfaceInfo interfaceInfo = interfaceInfoService.getInterfaceInfo(originUrl);

        // 获取secretKey
        User invokeUser = innerUserService.getInvokeUser(accessKey);
        if (null == invokeUser){
            log.error("调用用户不存在");
            throw new BusinessException(ErrorCode.OPERATION_ERROR , "调用账户异常");
        }
        String secretKey = invokeUser.getSecretKey();
        if (StringUtils.isEmpty(secretKey)){
            log.error("该用户密钥为空");
            throw new BusinessException(ErrorCode.OPERATION_ERROR , "无权限");
        }


        String needSK = SignUtil.doSign(secretKey);
        if (!needSK.equals(sign)){
            log.error("密钥不匹配");
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR , "无权限");
        }

        //5. 转发调用接口
        return handleResponse(exchange , chain ,  interfaceInfo.getId(), invokeUser.getId() , needSK);
    }

    @Override
    public int getOrder() {
        return -2;
    }

    /**
     * 验证参数
     * @param request
     */
    public void validateRequest(ServerHttpRequest request){
        //1. 验证请求头中必要的参数
        String accessKey = request.getHeaders().getFirst(HeadersConstant.ACCESS_KEY);
        String sign = request.getHeaders().getFirst(HeadersConstant.SIGN);
        String originUrl = request.getHeaders().getFirst(HeadersConstant.INVOKE_INTERFACE);
        String time = request.getHeaders().getFirst(HeadersConstant.TIMESTAMP);
        if (StringUtils.isAnyEmpty(accessKey , sign , originUrl , time)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR , "禁止访问,缺少必要参数");
        }
        Long requestTime = Long.parseLong(time);
        //2. 防止恶意请求以及回放 使用时间戳
        Long nowTime = System.currentTimeMillis() / 1000;
        if (nowTime - requestTime > THREE_MINUTES){
            throw new BusinessException(ErrorCode.OPERATION_ERROR , "会话已过期");
        }
    }

    /**
     * 处理响应
     *
     * @param exchange
     * @param chain
     * @return
     */
    public Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain, long interfaceInfoId, long userId , String needSk) {
        try {
            ServerHttpResponse originalResponse = exchange.getResponse();
            // 缓存数据的工厂
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            // 拿到响应码
            HttpStatus statusCode = originalResponse.getStatusCode();
            if (statusCode == HttpStatus.OK) {
                // 装饰，增强能力
                ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                    // 等调用完转发的接口后才会执行
                    @Override
                    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                        log.info("body instanceof Flux: {}", (body instanceof Flux));
                        if (body instanceof Flux) {
                            Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                            // 往返回值里写数据
                            // 拼接字符串
                            return super.writeWith(
                                    fluxBody.map(dataBuffer -> {
                                        try {
                                            userInterfaceInfoService.doInvokeCount(userId, interfaceInfoId);
                                        } catch (Exception e) {
                                            log.error("invokeCount error", e);
                                        }
                                        byte[] content = new byte[dataBuffer.readableByteCount()];
                                        dataBuffer.read(content);
                                        DataBufferUtils.release(dataBuffer);//释放掉内存
                                        // 构建日志
                                        StringBuilder sb2 = new StringBuilder(200);
                                        List<Object> rspArgs = new ArrayList<>();
                                        rspArgs.add(originalResponse.getStatusCode());
                                        String data = new String(content, StandardCharsets.UTF_8); //data
                                        sb2.append(data);
                                        // 打印日志
                                        log.info("响应结果：" + data);
                                        return bufferFactory.wrap(content);
                                    }));
                        } else {
                            // 8. 调用失败，返回一个规范的错误码
                            log.error("<--- {} 响应code异常", getStatusCode());
                        }
                        return super.writeWith(body);
                    }


                };
                //进行流量染色
                ServerHttpRequest request = exchange.getRequest();
                String source = SignUtil.doSign(needSk);
                request.mutate().header("source" , source).header("sign" , needSk);
                // 设置 response 对象为装饰过的 配置流量染色
                return chain.filter(exchange.mutate().response(decoratedResponse).request(request).build());
            }
            return chain.filter(exchange); // 降级处理返回数据
        } catch (Exception e) {
            log.error("网关处理响应异常" + e);
            return chain.filter(exchange);
        }
    }

    public void preventFrequentRequest(ServerHttpRequest request){
        String remoteAddress = request.getRemoteAddress().getHostString();
        String redisKey = RedisConstant.Second_ACCESS + remoteAddress + ':';
        //查看是否有记录
        String record  = redisTemplate.opsForValue().get(redisKey);
        //记录操作时间
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        //为空则插入
        if (StringUtils.isEmpty(record)){
            setFirstCount(redisKey);
        }else{
            //剩余过期时间
            Long restExpire = redisTemplate.getExpire(redisKey);
            if (null == restExpire){
                setFirstCount(redisKey);
                return;
            }

            //不为空校验是否超出最大请求次数
            RedisAccess redisAccess = JSONUtil.toBean(record, RedisAccess.class);
            Integer count = redisAccess.getCount();
            Integer maxCount = redisAccess.getMaxCount();
            if (count >= maxCount){
                throw new BusinessException(ErrorCode.FORBIDDEN_ERROR , "请求频繁请稍后再试");
            }

            //更新记录
            redisAccess.setCount(count + 1);
            stopWatch.stop();
            long costTime = stopWatch.getLastTaskTimeMillis() / 1000;
            //如果此时已经过期则重新插入
            if (restExpire < costTime){
                setFirstCount(redisKey);
                return;
            }
            redisTemplate.opsForValue().set(redisKey , JSONUtil.toJsonStr(redisAccess) , restExpire - costTime , TimeUnit.SECONDS);
        }
    };

    /**
     * 处理第一次请求记入redis
     * @param redisKey
     */
    public void setFirstCount(String redisKey){
        RedisAccess redisAccess = new RedisAccess();
        redisAccess.setCount(1);
        redisAccess.setMaxCount(RedisConstant.MAX_COUNT);
        redisAccess.setPeriod(RedisConstant.PERIOD);
        String jsonStr = JSONUtil.toJsonStr(redisAccess);
        redisTemplate.opsForValue().set(redisKey , jsonStr , RedisConstant.PERIOD , TimeUnit.SECONDS);
    }

    /**
     * 打印请求日志
     */
    public void requestLog(ServerHttpRequest request){
        log.info("请求方式:" + request.getMethod() + '\n');
        log.info("请求url:" + request.getPath() + '\n');
        log.info("请求uri" + request.getURI() + '\n');
        log.info("请求ip:" + request.getRemoteAddress() + '\n');
        log.info("请求参数:" + request.getQueryParams() + '\n');
        log.info("请求ak:" + request.getHeaders().get(HeadersConstant.ACCESS_KEY) + '\n');
        log.info("请求sign:" + request.getHeaders().get(HeadersConstant.SIGN) + '\n');
        log.info("请求onceNum:" + request.getHeaders().get(HeadersConstant.ONCE_NUM) + '\n');
        log.info("请求timeStamp:" + request.getHeaders().get(HeadersConstant.TIMESTAMP) + '\n');
    }


}
